#include "helper.h"
#include "monitor.h"
#include "reg.h"
#include "memory.h"
#include "goldentrace.h"

extern uint32_t instr;
extern char assembly[80];

/* decode I-type instrucion with unsigned immediate */
static void decode_imm_type(uint32_t instr) {

	//rs
	op_src1->type = OP_TYPE_REG;
	op_src1->reg = (instr & RS_MASK) >> (RT_SIZE + IMM_SIZE);
	op_src1->val = reg_w(op_src1->reg);

	//imm
	op_src2->type = OP_TYPE_IMM;
	op_src2->imm = instr & IMM_MASK;
	op_src2->val = op_src2->imm;

	//rt
	op_dest->type = OP_TYPE_REG;
	op_dest->reg = (instr & RT_MASK) >> (IMM_SIZE);
	op_dest->val = reg_w(op_dest->reg);
}

int32_t sign_extend( int32_t val) {

	int32_t num = val;
	int32_t temp = (val >> 15) & 0x00000001;
	if (temp == 0x00000000)		return num;
	else {
		num = num | 0xffff0000;
		return num;
	}
}

make_helper(addi) {

	decode_imm_type(instr);

	int32_t imm =  (int32_t)sign_extend(op_src2->val);
	int32_t sign1 = ((op_src1->val) & 0x80000000) >> 31;
	int32_t sign2 = (imm & 0x80000000) >> 31;
	int32_t temp = ((int32_t)op_src1->val + imm);
	int32_t temp_sign = (temp & 0x80000000) >> 31;

	if ( (sign1 == sign2) && temp_sign != sign1) {
		
		//overflow
		uint32_t temp = cp0.Cause & ~(0x8000007c);
		temp |= (0x0c << 2);
		cp0.Cause = temp;

		uint32_t tempp = cp0.Status & ~(0x00000002);
		tempp |= 0x2;
		cp0.Status = tempp;


		cp0.EPC = cpu.pc;
		printf("addi overflow\n");
		cpu.pc = 0xbfc00380;

	}

	else {
		reg_w(op_dest->reg) = temp;
		sprintf(assembly, "addi   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2 -> imm);
		fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
	}
	

}

make_helper(addiu) {

	decode_imm_type(instr);
	reg_w(op_dest->reg) = (op_src1->val + sign_extend(op_src2->imm));
	sprintf(assembly, "addiu   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2 -> imm);
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(slti) {

	decode_imm_type(instr);
	if(op_src1->val < sign_extend(op_src2->imm))	reg_w(op_dest->reg) = 0x1;
	else	reg_w(op_dest->reg) = 0x0;
	sprintf(assembly, "slti   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2 -> imm);
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(sltiu) {

	decode_imm_type(instr);
	if( (unsigned) op_src1->val < (unsigned) sign_extend(op_src2->imm))		reg_w(op_dest->reg) = 0x1;
	else	reg_w(op_dest->reg) = 0x0;
	sprintf(assembly, "sltiu   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2 -> imm);
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(andi) {

	decode_imm_type(instr);
	reg_w(op_dest->reg) = (op_src1->val & op_src2->imm);
	sprintf(assembly, "andi   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2 -> imm);
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(lui) {

	decode_imm_type(instr);
	reg_w(op_dest->reg) = (op_src2->val << 16);
	sprintf(assembly, "lui   %s,   0x%04x", REG_NAME(op_dest->reg), op_src2->imm);
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(ori) {

	decode_imm_type(instr);
	reg_w(op_dest->reg) = (op_src1->val | op_src2->val);
	sprintf(assembly, "ori   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(xori) {
	decode_imm_type(instr);
	reg_w(op_dest->reg) = (op_src1->val ^ op_src2->imm);
	sprintf(assembly, "xori   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2 -> imm);
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(beq) {

	decode_imm_type(instr);
	int32_t target_offset = (sign_extend((int32_t)op_src2->imm ) << 2 );
	if((int32_t)op_dest->val == (int32_t)op_src1->val)
		cpu.pc = cpu.pc + target_offset;
	sprintf(assembly, "beq   %s,   %s,   0x%04x", REG_NAME(op_src1->reg), REG_NAME(op_dest->reg), op_src2 -> imm);
}

make_helper(bne) {

	decode_imm_type(instr);
	int32_t target_offset = (sign_extend((int32_t)op_src2->imm ) << 2 );
	if((int32_t)op_dest->val != (int32_t)op_src1->val)
		cpu.pc = cpu.pc + target_offset;
	sprintf(assembly, "bne   %s,   %s,   0x%04x", REG_NAME(op_src1->reg), REG_NAME(op_dest->reg), op_src2 -> imm);
}

make_helper(bgez) {

	//decode_imm_type(instr);
	int32_t target_offset = (sign_extend((int32_t)op_src2->imm ) << 2 );
	if((int32_t)op_src1->val >= 0)
		cpu.pc = cpu.pc + target_offset;
	sprintf(assembly, "bgez   %s,   0x%04x", REG_NAME(op_src1->reg), op_src2 -> imm);
}

make_helper(bgtz) {

	decode_imm_type(instr);
	int32_t target_offset = (sign_extend((int32_t)op_src2->imm ) << 2 );
	if((int32_t)op_src1->val > 0)
		cpu.pc = cpu.pc + target_offset;
	sprintf(assembly, "bgtz   %s,   0x%04x", REG_NAME(op_src1->reg), op_src2 -> imm);
}

make_helper(blez) {

	decode_imm_type(instr);
	int32_t target_offset = (sign_extend((int32_t)op_src2->imm ) << 2 );
	if((int32_t)op_src1->val <= 0)
		cpu.pc = cpu.pc + target_offset;
	sprintf(assembly, "blez   %s,   0x%04x", REG_NAME(op_src1->reg), op_src2 -> imm);
}

make_helper(bltz) {

	//decode_imm_type(instr);
	int32_t target_offset = (sign_extend((int32_t)op_src2->imm ) << 2 );
	if((int32_t)op_src1->val < 0)
		cpu.pc = cpu.pc +  target_offset;
	sprintf(assembly, "bltz   %s,   0x%04x", REG_NAME(op_src1->reg), op_src2 -> imm);
}

make_helper(bgezal) {

	//decode_imm_type(instr);
	int32_t target_offset = (sign_extend((int32_t)op_src2->imm ) << 2 );
	cpu.ra = cpu.pc + 8;
	if((int32_t)op_src1->val >= 0)
		cpu.pc = cpu.pc + target_offset;
	sprintf(assembly, "bgezal   %s,   0x%04x", REG_NAME(op_src1->reg), op_src2 -> imm);
	//fprintf(GoldenTrace,"xxxxxxxxx\n");
}

make_helper(bltzal) {

	//decode_imm_type(instr);
	int32_t target_offset = (sign_extend((int32_t)op_src2->imm ) << 2 );
	cpu.ra = cpu.pc + 8;
	if((int32_t)op_src1->val < 0)
		cpu.pc = cpu.pc + target_offset;
	sprintf(assembly, "bltzal   %s,   0x%04x", REG_NAME(op_src1->reg), op_src2 -> imm);
}

make_helper(jump) {

	decode_imm_type(instr);

	if(op_dest->reg == 0x00000001) {
		
		bgez(cpu.pc);
	}
	else if (op_dest->reg == 0x00000000) {
		
		bltz(cpu.pc);
	}
	else if (op_dest->reg == 0x000000011) {

		bgezal(cpu.pc);

	}
	else if (op_dest->reg == 0x00000010) {

		bltzal(cpu.pc);
	}
	
}

make_helper(lb) {

	decode_imm_type(instr);
	int32_t offset = (signed)sign_extend(op_src2->val);
	int32_t vaddr = (int32_t)op_src1->val + (signed)offset;
	int32_t temp = (int32_t)mem_read(vaddr, 1);
	if(temp & 0x00000080)
		reg_w(op_dest->reg) = (temp | 0xffffff00);
	else
		reg_w(op_dest->reg) = temp;
	sprintf(assembly, "lb   %s,   0x%04x(%s)", REG_NAME(op_dest->reg), op_src2 -> imm, REG_NAME(op_src1->reg));
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(lbu) {

	decode_imm_type(instr);
	uint32_t vaddr = (int32_t)op_src1->val + (int32_t)sign_extend(op_src2->val);
	reg_w(op_dest->reg) = (uint32_t)mem_read(vaddr, 1);
	sprintf(assembly, "lbu   %s,   0x%04x(%s)", REG_NAME(op_dest->reg), op_src2 -> imm, REG_NAME(op_src1->reg));
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(lh) {

	decode_imm_type(instr);
	uint32_t vaddr = (int32_t)op_src1->val + (int32_t)sign_extend(op_src2->val);
	if(vaddr & 0x00000001) {

		//SignalException(AddressError)
		uint32_t temp = cp0.Cause & ~(0x8000007c);
		temp |= (0x04 << 2);
		cp0.Cause = temp;

		uint32_t tempp = cp0.Status & ~(0x00000002);
		tempp |= 0x2;
		cp0.Status = tempp;

		cp0.BadVAddr = vaddr;
		cp0.EPC = cpu.pc;
		printf("load addr error\n");
		cpu.pc = 0xbfc00380;
	}
	else {

	int32_t temp = (int32_t)mem_read(vaddr, 2);
	if(temp & 0x00008000)
		reg_w(op_dest->reg) = (temp | 0xffff0000);
	else
		reg_w(op_dest->reg) = temp;
	sprintf(assembly, "lh   %s,   0x%04x(%s)", REG_NAME(op_dest->reg), op_src2 -> imm, REG_NAME(op_src1->reg));
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
	}
}

make_helper(lhu) {

	decode_imm_type(instr);
	uint32_t vaddr = (int32_t)op_src1->val +(int32_t) sign_extend(op_src2->val);
	if(vaddr & 0x00000001) {

		//SignalException(AddressError)
		uint32_t temp = cp0.Cause & ~(0x8000007c);
		temp |= (0x04 << 2);
		cp0.Cause = temp;

		uint32_t tempp = cp0.Status & ~(0x00000002);
		tempp |= 0x2;
		cp0.Status = tempp;

		cp0.BadVAddr = vaddr;
		cp0.EPC = cpu.pc;
		printf("load addr error\n");
		cpu.pc = 0xbfc00380;
	}
	else {

	reg_w(op_dest->reg) = (uint32_t)mem_read(vaddr, 2);
	sprintf(assembly, "lhu   %s,   0x%04x(%s)", REG_NAME(op_dest->reg), op_src2 -> imm, REG_NAME(op_src1->reg));
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
	}
}

make_helper(lw) {

	decode_imm_type(instr);
	uint32_t vaddr = (int32_t)op_src1->val + (int32_t)sign_extend(op_src2->val);
	if(vaddr & 0x00000011) {

		//SignalException(AddressError)

		uint32_t temp = cp0.Cause & ~(0x8000007c);
		temp |= (0x04 << 2);
		cp0.Cause = temp;

		uint32_t tempp = cp0.Status & ~(0x00000002);
		tempp |= 0x2;
		cp0.Status = tempp;

		cp0.BadVAddr = vaddr;
		cp0.EPC = cpu.pc;
		printf("load addr error\n");
		cpu.pc = 0xbfc00380;

	}
	else {

	uint32_t temp = mem_read(vaddr, 4);
	reg_w(op_dest->reg) = (int32_t)temp;
	
	sprintf(assembly, "lw   %s,   0x%04x(%s)", REG_NAME(op_dest->reg), op_src2 -> imm, REG_NAME(op_src1->reg));
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
	}
}

make_helper(sb) {

	decode_imm_type(instr);
	uint32_t vaddr = op_src1->val + sign_extend(op_src2->val);
	uint8_t data = reg_b(op_dest->reg);
	mem_write(vaddr, 1, data);
	sprintf(assembly, "sb   %s,   0x%04x(%s)", REG_NAME(op_dest->reg), op_src2 -> imm, REG_NAME(op_src1->reg));

}

make_helper(sh) {

	decode_imm_type(instr);
	uint32_t vaddr = op_src1->val + sign_extend(op_src2->val);
	if(vaddr & 0x00000001) {

		//SignalException(AddressError)

		uint32_t temp = cp0.Cause & ~(0x8000007c);
		temp |= (0x05 << 2);
		cp0.Cause = temp;

		uint32_t tempp = cp0.Status & ~(0x00000002);
		tempp |= 0x2;
		cp0.Status = tempp;

		cp0.BadVAddr = vaddr;
		cp0.EPC = cpu.pc;
		printf("write addr error\n");
		cpu.pc = 0xbfc00380;
	}
	else {

	uint16_t data = reg_h(op_dest->reg);
	mem_write(vaddr, 2, data);
	sprintf(assembly, "sh   %s,   0x%04x(%s)", REG_NAME(op_dest->reg), op_src2 -> imm, REG_NAME(op_src1->reg));

	}
	
}

make_helper(sw) {

	decode_imm_type(instr);
	uint32_t vaddr = op_src1->val + sign_extend(op_src2->val);
	if(vaddr & 0x00000011) {

		
		//SignalException(AddressError)
		uint32_t temp = cp0.Cause & ~(0x8000007c);
		temp |= (0x05 << 2);
		cp0.Cause = temp;

		uint32_t tempp = cp0.Status & ~(0x00000002);
		tempp |= 0x2;
		cp0.Status = tempp;

		cp0.BadVAddr = vaddr;
		cp0.EPC = cpu.pc;
		printf("write addr error\n");
		cpu.pc = 0xbfc00380;
	}
	else {

	uint32_t data = reg_w(op_dest->reg);
	mem_write(vaddr, 4, data);
	sprintf(assembly, "sw   %s,   0x%04x(%s)", REG_NAME(op_dest->reg), op_src2 -> imm, REG_NAME(op_src1->reg));
	}
	
}